package hos.http;

import android.app.Application;
import android.content.Context;
import android.os.Looper;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;

import hos.http.converter.HttpConvert;
import hos.http.retrofit.HttpServiceMethod;
import hos.http.retrofit.RetrofitServiceMethod;

/**
 * <p>Title: HttpConfig </p>
 * <p>Description:  </p>
 * <p>Company: www.mapuni.com </p>
 *
 * @author : 蔡俊峰
 * @version : 1.0
 * @date : 2023-02-23 16:32
 */
public class HttpConfig {

    /**
     * 访问地址
     */
    private String host;
    /**
     * 是否启用日志
     */
    private boolean debug = true;
    /**
     * 结果转换器
     */
    private HttpConvert<?> httpConvert;

    /**
     * 请求拦截器
     */
    private HttpInterceptor.RequestInterceptor requestInterceptor;
    /**
     * 响应拦截器
     */
    private HttpInterceptor.ResponseInterceptor responseInterceptor;
    /**
     * 异常拦截器
     */
    private HttpInterceptor.ExceptionInterceptor exceptionInterceptor;
    /**
     * 请求Header
     */
    private final Map<String, String> header = new HashMap<>();
    /**
     * 请求参数
     */
    private final List<HttpParam> queryParameters = new ArrayList<>();
    /**
     * 请求参数
     */
    private final List<HttpParam> bodyParameters = new ArrayList<>();

    private final Map<String, Object> extra = new HashMap<>();

    private HttpCall.Factory factory;
    /// 重试次数
    private int retryCount = 0;
    private int connectTimeout = 120;
    private int readTimeout = 120;
    private int writeTimeout = 120;
    // 命名空间
    private String nameSpace;

    private HttpReturn<HttpBaseRequest<?>> request;

    private HttpReturn<HttpBaseRequestBody<?>> requestBody;

    private HttpReturn<HttpBaseRequestSoap<?>> requestSoap;

    private HttpReturn<HttpBaseRequestDownload<?>> requestDownload;

    private HttpReturn<RetrofitServiceMethod<?>> retrofitServiceMethod;

    private final ConcurrentHashMap<Method, Object> serviceMethodCache = new ConcurrentHashMap<>();

    private Executor callBack = null;
    private Executor mainThread = null;
    private Executor thread = null;

    private static HttpConfig config;

    private Context context;

    /**
     * 请求对话框构建工厂
     */
    private HttpDialogFactory dialogFactory;

    private HttpConfig() {
    }

    public static HttpConfig config() {
        if (config == null) {
            return config = new HttpConfig();
        }
        return config;
    }

    //<editor-fold desc="init">
    public HttpConfig init(Application context, String host, HttpCall.Factory factory) {
        this.context = context;
        this.host = host;
        this.factory = factory;
        return this;
    }

    public HttpConfig init(Application context, String host, HttpCall.Factory factory, boolean debug, int retryCount) {
        this.context = context;
        this.host = host;
        this.factory = factory;
        this.debug = debug;
        this.retryCount = retryCount;
        return this;
    }

    public HttpConfig init(Application context, String host, HttpCall.Factory factory, HttpConvert<?> httpConvert) {
        this.context = context;
        this.host = host;
        this.factory = factory;
        this.httpConvert = httpConvert;
        return this;
    }

    public HttpConfig init(Application context, String host, HttpCall.Factory factory, HttpConvert<?> httpConvert, boolean debug, int retryCount) {
        this.context = context;
        this.host = host;
        this.factory = factory;
        this.httpConvert = httpConvert;
        this.debug = debug;
        this.retryCount = retryCount;
        return this;
    }
    //</editor-fold>

    //<editor-fold desc="Executor">
    public HttpConfig callBack(Executor callBackMain) {
        this.callBack = callBackMain;
        return this;
    }

    public Executor getCallBack() {
        if (callBack == null) {
            callBack = new AndroidMainExecutor();
        }
        return callBack;
    }

    public HttpConfig mainThread(Executor callBackMain) {
        this.mainThread = callBackMain;
        return this;
    }

    public Executor getMainThread() {
        if (mainThread == null) {
            mainThread = new AndroidMainExecutor();
        }
        return mainThread;
    }

    public HttpConfig thread(Executor thread) {
        this.thread = thread;
        return this;
    }

    public Executor getThread() {
        if (thread == null) {
            thread = HttpThreadExecutor.get();
        }
        return thread;
    }

    public static boolean isMainThread() {
        return Looper.getMainLooper().getThread() == Thread.currentThread();
    }

    public static void runIo(Runnable runnable) {
        if (isMainThread()) {
            config().getThread().execute(runnable);
            return;
        }
        runnable.run();
    }

    public static void runUI(Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
            return;
        }
        config().getMainThread().execute(runnable);
    }

    //</editor-fold>

    public HttpConfig setDialogFactory(HttpDialogFactory dialogFactory) {
        this.dialogFactory = dialogFactory;
        return this;
    }

    public HttpDialogFactory getDialogFactory() {
        if (dialogFactory == null) {
            dialogFactory = new HttpDialogFactory.DEFAULT();
        }
        return dialogFactory;
    }

    public HttpConfig changeFactory(HttpCall.Factory factory) {
        return changeFactory(factory, null);
    }

    public HttpConfig changeFactory(HttpCall.Factory factory, HttpConvert<?> httpConvert) {
        if (factory != null) {
            this.factory = factory;
        }
        if (httpConvert != null) {
            this.httpConvert = httpConvert;
        }
        return this;
    }

    public HttpConfig changeRequest(HttpReturn<HttpBaseRequest<?>> request,
                                    HttpReturn<HttpBaseRequestBody<?>> requestBody,
                                    HttpReturn<HttpBaseRequestSoap<?>> requestSoap,
                                    HttpReturn<HttpBaseRequestDownload<?>> requestDownload) {
        if (request != null) {
            this.request = request;
        }
        if (requestBody != null) {
            this.requestBody = requestBody;
        }
        if (request != null) {
            this.requestSoap = requestSoap;
        }
        if (requestBody != null) {
            this.requestDownload = requestDownload;
        }
        return this;
    }

    //<editor-fold desc="Request">
    public HttpBaseRequest<?> getRequest() {
        if (request == null) {
            return new HttpRequest<>();
        }
        return request.invoke();
    }

    public HttpConfig setRequest(HttpReturn<HttpBaseRequest<?>> request) {
        this.request = request;
        return this;
    }

    public HttpBaseRequestBody<?> getRequestBody() {
        if (requestBody == null) {
            return new HttpRequestBody();
        }
        return requestBody.invoke();
    }

    public HttpConfig setRequestBody(HttpReturn<HttpBaseRequestBody<?>> requestBody) {
        this.requestBody = requestBody;
        return this;
    }

    public HttpBaseRequestSoap<?> getRequestSoap() {
        if (requestSoap == null) {
            return new HttpRequestSoap();
        }
        return requestSoap.invoke();
    }

    public HttpConfig setRequestSoap(HttpReturn<HttpBaseRequestSoap<?>> requestSoap) {
        this.requestSoap = requestSoap;
        return this;
    }

    public HttpBaseRequestDownload<?> getRequestDownload() {
        if (requestDownload == null) {
            return new HttpRequestDownload();
        }
        return requestDownload.invoke();
    }

    public HttpConfig setRequestDownload(HttpReturn<HttpBaseRequestDownload<?>> requestDownload) {
        this.requestDownload = requestDownload;
        return this;
    }

    public RetrofitServiceMethod<?> getRetrofitServiceMethod() {
        if (retrofitServiceMethod == null) {
            return new HttpServiceMethod<>();
        }
        return retrofitServiceMethod.invoke();
    }

    public HttpConfig setRetrofitServiceMethod(HttpReturn<RetrofitServiceMethod<?>> requestDownload) {
        this.retrofitServiceMethod = requestDownload;
        return this;
    }

    //</editor-fold>

    public String getHost() {
        return host;
    }

    public boolean isDebug() {
        return debug;
    }


    //<editor-fold desc="Param">
    ///添加header

    public String getNameSpace() {
        if (nameSpace == null || nameSpace.length() == 0) {
            return nameSpace = "http://tempuri.org/";
        }
        return nameSpace;
    }

    public HttpConfig setNameSpace(String nameSpace) {
        this.nameSpace = nameSpace;
        return this;
    }

    public HttpConfig header(Map<String, String> header) {
        this.header.putAll(header);
        return this;
    }

    ///添加 Body
    public HttpConfig query(Map<String, Object> param) {
        this.queryParameters.addAll(HttpParam.create(param));
        return this;
    }

    ///添加 Body
    public HttpConfig param(Map<String, Object> param) {
        this.bodyParameters.addAll(HttpParam.create(param));
        return this;
    }

    public Map<String, String> header() {
        return header;
    }

    public HttpConfig header(String k, String v) {
        header.put(k, v);
        return this;
    }

    public List<HttpParam> query() {
        return queryParameters;
    }

    public HttpConfig query(String k, Object v) {
        queryParameters.add(new HttpParam(k, v));
        return this;
    }

    public HttpConfig query(String k, Object v, boolean encoded) {
        queryParameters.add(new HttpParam(k, v, encoded));
        return this;
    }

    public List<HttpParam> params() {
        return bodyParameters;
    }

    public HttpConfig param(String k, Object v) {
        bodyParameters.add(new HttpParam(k, v));
        return this;
    }

    public HttpConfig param(String k, Object v, boolean encoded) {
        bodyParameters.add(new HttpParam(k, v, encoded));
        return this;
    }

    public Map<String, Object> getExtra() {
        return extra;
    }

    ///添加 额外参数
    public HttpConfig extra(Map<String, Object> extra) {
        this.extra.putAll(extra);
        return this;
    }

    ///添加 额外参数
    public HttpConfig extra(String key, Object value) {
        this.extra.put(key, value);
        return this;
    }

    //</editor-fold>

    public HttpConvert<?> getHttpConvert() {
        return httpConvert;
    }

    //<editor-fold desc="interceptor">
    public HttpInterceptor.RequestInterceptor getRequestInterceptor() {
        return requestInterceptor;
    }

    public HttpConfig interceptor(HttpInterceptor.RequestInterceptor requestInterceptor) {
        this.requestInterceptor = requestInterceptor;
        return this;
    }

    public HttpInterceptor.ResponseInterceptor getResponseInterceptor() {
        return responseInterceptor;
    }

    public HttpConfig interceptor(HttpInterceptor.ResponseInterceptor responseInterceptor) {
        this.responseInterceptor = responseInterceptor;
        return this;
    }

    public HttpInterceptor.ExceptionInterceptor getExceptionInterceptor() {
        return exceptionInterceptor;
    }

    public HttpConfig interceptor(HttpInterceptor.ExceptionInterceptor exceptionInterceptor) {
        this.exceptionInterceptor = exceptionInterceptor;
        return this;
    }

    public HttpConfig setHttpConvert(HttpConvert httpConvert) {
        this.httpConvert = httpConvert;
        return this;
    }

    //</editor-fold>


    //<editor-fold desc="timeout">

    public int getRetryCount() {
        return retryCount;
    }

    public int getConnectTimeout() {
        return connectTimeout;
    }

    public int getReadTimeout() {
        return readTimeout;
    }

    public int getWriteTimeout() {
        return writeTimeout;
    }

    public HttpConfig timeout(int connectTimeout, int readTimeout, int writeTimeout) {
        this.writeTimeout = writeTimeout;
        this.readTimeout = readTimeout;
        this.connectTimeout = connectTimeout;
        return this;
    }
    //</editor-fold>

    public HttpCall.Factory getFactory() {
        return factory;
    }

    public HttpConfig debug(boolean debug) {
        this.debug = debug;
        return this;
    }

    public Context getContext() {
        return context;
    }


    /**
     * 缓存获取请求方法
     */
    @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
    RetrofitServiceMethod<?> loadServiceMethod(Class<?> service, Method method) {
        while (true) {
            // Note: Once we are minSdk 24 this whole method can be replaced by computeIfAbsent.
            Object lookup = serviceMethodCache.get(method);

            if (lookup instanceof RetrofitServiceMethod<?>) {
                // Happy path: method is already parsed into the model.
                return (RetrofitServiceMethod<?>) lookup;
            }

            if (lookup == null) {
                // Map does not contain any value. Try to put in a lock for this method. We MUST synchronize
                // on the lock before it is visible to others via the map to signal we are doing the work.
                Object lock = new Object();
                synchronized (lock) {
                    lookup = serviceMethodCache.putIfAbsent(method, lock);
                    if (lookup == null) {
                        // On successful lock insertion, perform the work and update the map before releasing.
                        // Other threads may be waiting on lock now and will expect the parsed model.
                        RetrofitServiceMethod<Object> result;
                        try {
                            //noinspection unchecked
                            RetrofitServiceMethod<Object> serviceMethod = (RetrofitServiceMethod<Object>) getRetrofitServiceMethod();
                            result = serviceMethod.parse(service, method);
                        } catch (Throwable e) {
                            // Remove the lock on failure. Any other locked threads will retry as a result.
                            serviceMethodCache.remove(method);
                            throw e;
                        }
                        serviceMethodCache.put(method, result);
                        return result;
                    }
                }
            }

            // Either the initial lookup or the attempt to put our lock in the map has returned someone
            // else's lock. This means they are doing the parsing, and will update the map before
            // releasing
            // the lock. Once we can take the lock, the map is guaranteed to contain the model or null.
            // Note: There's a chance that our effort to put a lock into the map has actually returned a
            // finished model instead of a lock. In that case this code will perform a pointless lock and
            // redundant lookup in the map of the same instance. This is rare, and ultimately harmless.
            synchronized (lookup) {
                Object result = serviceMethodCache.get(method);
                if (result == null) {
                    // The other thread failed its parsing. We will retry (and probably also fail).
                    continue;
                }
                return (RetrofitServiceMethod<?>) result;
            }
        }
    }
}
